package nacos

import (
	"context"
	"fmt"
	"github.com/nacos-group/nacos-sdk-go/v2/model"
	"google.golang.org/grpc/resolver"
	"sort"
	"sync"
	"time"
)

type NacosResolver struct {
	cancelFunc context.CancelFunc
	cc         resolver.ClientConn
	pipe       chan []resolver.Address
	ctx        context.Context
	ticker     *time.Ticker
	wg         sync.WaitGroup
}

func (r *NacosResolver) ResolveNow(options resolver.ResolveNowOptions) {

}

func (r *NacosResolver) Close() {
	r.cancelFunc()
	r.ticker.Stop()
	r.wg.Wait()
}

func (r *NacosResolver) callBackHandle(services []model.Instance, err error) {
	if err != nil {
		//logger.Error("[Nacos resolver] watcher call back handle error:%v", err)
		return
	}
	ee := make([]resolver.Address, 0, len(services))
	for _, s := range services {
		address := resolver.Address{Addr: fmt.Sprintf("%s:%d", s.Ip, s.Port), Metadata: s.Metadata}
		ee = append(ee, address)
		//if s.Metadata != nil && s.Metadata["gRPC_port"] != "" {
		//	ee = append(ee, fmt.Sprintf("%s:%s", s.Ip, s.Metadata["gRPC_port"]))
		//} else {
		//	ee = append(ee, fmt.Sprintf("%s:%d", s.Ip, s.Port))
		//}
	}
	r.pipe <- ee
}

func (r *NacosResolver) populateEndpoints() {
	defer r.wg.Done()
	for {
		select {
		case cc := <-r.pipe:
			//connsSet := make(map[string]struct{}, len(cc))
			//for _, c := range cc {
			//	connsSet[c] = struct{}{}
			//}
			//conns := make([]resolver.Address, 0, len(cc))
			//for c := range connsSet {
			//	conns = append(conns, resolver.Address{Addr: c})
			//}
			sort.Sort(byAddressString(cc)) // Don't replace the same address list in the balancer
			_ = r.cc.UpdateState(resolver.State{Addresses: cc})

		case <-r.ctx.Done():
			//logx.Info("[Nacos resolver] Watch has been finished")
			return
		}
	}
}

func (r *NacosResolver) selectInstances(fn func() ([]model.Instance, error)) {
	defer r.wg.Done()
	for _ = range r.ticker.C {
		instances, err := fn()
		r.callBackHandle(instances, err)
	}
}

type byAddressString []resolver.Address

func (p byAddressString) Len() int           { return len(p) }
func (p byAddressString) Less(i, j int) bool { return p[i].Addr < p[j].Addr }
func (p byAddressString) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
